#ifndef PW_PBL_META_ENUM_HPP
#define PW_PBL_META_ENUM_HPP

#include "Auto.hpp"
#include "VHStr.hpp"
#include "../PackIter.hpp"

namespace pw_pbl::Meta
{
	template <Str... Names>
	struct Index
	{
		static constexpr size_t ESize = sizeof...(Names);
		using value_type = std::decay_t<decltype(NarrowUIntFor<ESize - 1>)>;
		value_type value;

		constexpr operator value_type() const { return value; }

		template <size_t N>
		consteval Index(const char (&name)[N]) : value(0)
		{
			std::string_view str{name};
			for (; value < ESize; value++)
				if (str == names_cstr[value])
					return;
			std::abort(); // name not found
		}
		static constexpr Index unchecked_new(value_type v) { return Index{v}; }

	private:
		explicit constexpr Index(value_type i) : value(i) {}

		static constexpr auto names_cstr = std::array{Names.data()...};
	};

	template <typename T, Meta::Str... NAMES>
	struct NamedArray
	{
		static constexpr auto ESize = sizeof...(NAMES);
		std::array<T, ESize> raw;
		constexpr NamedArray(std::array<T, ESize> _data) : raw(std::move(_data)) {}
		constexpr NamedArray() requires std::is_default_constructible_v<T> = default;

		template <typename SELF>
		constexpr decltype(auto) operator[](this SELF &&self, Index<NAMES...> index) { return std::forward_like<SELF>(self.raw[index.value]); }
	};

	template <NVPair... NVs>
	struct ExEnum;

	template <Str... Names>
	struct Enum
	{
		using Natural = Enum;
		static constexpr bool ENatural = true;
		static constexpr auto size = sizeof...(Names);
		static constexpr auto max_index = NarrowUIntFor<size - 1>;

		template <Str Name>
		static constexpr bool has_name = (pack_iter::find << ... << (Name == Names)) < size;

		using Index = Index<Names...>;

		template <Str Name>
		static constexpr Index index{Index::unchecked_new((pack_iter::unique_find << ... << (Name == Names)))};

		template <typename T>
		using Array = NamedArray<T, Names...>;

		template <typename T>
		static constexpr Array<T> make_array(std::array<T, size> ary) { return {std::move(ary)}; }

		template <size_t INDEX> requires((INDEX < sizeof...(Names)))
		static constexpr auto name = Str{Names...[INDEX]()};

		static constexpr auto names_cstr = make_array(std::array{Names.data()...});

		static constexpr auto names_vhstr = make_array(std::array{VHStrPtr{MetaVHStr<Names>}...});

		template <Auto... V>
		using Custom = ExEnum<{Names, V()}...>;

		template <template <Auto...> class TPL>
		using apply = TPL<Names...>;

		template <Str... NEW_NAMES>
		using append = Enum<Names..., NEW_NAMES...>;

		template <Str... NEW_NAMES>
		using prepend = Enum<NEW_NAMES..., Names...>;

		template <auto F>
		static constexpr auto map = make_array(std::array{F(MetaValue<Names>)...});

		template <auto F>
		static constexpr auto map_with_id = []<size_t... ids>(std::integer_sequence<size_t, ids...>) {
			return make_array(std::array{F(MetaValue<ids, Names>)...});
		}(std::make_index_sequence<size>{});
	};

	template <NVPair... NVs>
	struct ExEnum : public Enum<NVs.name...>
	{
		using Natural = Enum<NVs.name...>;
		static constexpr bool ENatural = false;
		static constexpr auto size = sizeof...(NVs);

		using Natural::make_array;

		template <Str Name>
		static constexpr auto value = NVs...[Natural::template index<Name>.value]().value();

		template <typename... T>
		static constexpr auto values = [] {
			if constexpr (sizeof...(T) == 0)
			{
				if constexpr ((std::integral<decltype(NVs.value())> && ...))
					return make_array(std::array<TIntFor::Values<NVs().value()...>, size>{NVs.value()...});
				else
					return make_array(std::array{NVs.value()...});
			}
			else
				return make_array(std::array<T..., size>{NVs.value()...});
		}();

		template <typename... T>
		static constexpr VHArray values_vharray{values<T...>.raw};

		template <Auto Value>
		static constexpr auto name = Str{NVs...[(pack_iter::unique_find << ... << (Value == NVs.value))]().name};

		template <template <Auto...> class TPL>
		using apply = TPL<NVs...>;

		template <auto F>
		static constexpr auto map_with_value = make_array(std::array{F(NVs)...});
	};

	inline namespace literals
	{
		template <NVPair... NVs>
		constexpr ExEnum<NVs...> MetaExEnum{};

		template <Str... Names>
		constexpr Enum<Names...> MetaEnum{};
	} // namespace literals
} // namespace pw_pbl::Meta

#endif // PW_PBL_METAENUM_HPP
